/*-------------------------------------------------------------------------
 *
 * cryptohash_openssl.c
 *	  支持加密哈希函数的OpenSSL上方的包装例程。
 *
 * 只有在编译代码时启用OpenSSL支持时才能使用。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *		  src/common/cryptohash_openssl.c
 *
 *-------------------------------------------------------------------------
 */

#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif

#include <openssl/err.h>
#include <openssl/evp.h>

#include "common/cryptohash.h"
#include "common/md5.h"
#include "common/sha1.h"
#include "common/sha2.h"
#ifndef FRONTEND
#include "utils/memutils.h"
#include "utils/resowner.h"
#include "utils/resowner_private.h"
#endif

/*
 * 在后端中，使用 TopMemoryContext 中的分配来处理资源所有者清理。
 * 在前端中，使用 malloc 以便能够将失败状态返回给调用者。
 */
#ifndef FRONTEND
#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
#define FREE(ptr) pfree(ptr)
#else
#define ALLOC(size) malloc(size)
#define FREE(ptr) free(ptr)
#endif

/* 错误状态集 */
typedef enum pg_cryptohash_errno
{
	PG_CRYPTOHASH_ERROR_NONE = 0,
	PG_CRYPTOHASH_ERROR_DEST_LEN,
	PG_CRYPTOHASH_ERROR_OPENSSL
} pg_cryptohash_errno;

/*
 * 内部 pg_cryptohash_ctx 结构。
 *
 * 这跟踪与后端每个 EVP 上下文数据相关的资源所有者。
 */
struct pg_cryptohash_ctx
{
	pg_cryptohash_type type;
	pg_cryptohash_errno error;
	const char *errreason;

	EVP_MD_CTX *evpctx;

#ifndef FRONTEND
	ResourceOwner resowner;
#endif
};

static const char * fc_SSLerrmessage(unsigned long fc_ecode)
{
	if (fc_ecode == 0)
		return NULL;

	/*
	 * 这可能返回 NULL，但如果是这种情况，我们将退回到默认错误路径。
	 */
	return ERR_reason_error_string(fc_ecode);
}

/*
 * pg_cryptohash_create
 *
 * 分配一个哈希上下文。 发生 OOM 时返回 NULL。 后端发出错误，而不返回。
 */
pg_cryptohash_ctx *
pg_cryptohash_create(pg_cryptohash_type fc_type)
{
	pg_cryptohash_ctx *fc_ctx;

	/*
	 * 确保资源所有者有空间记住此引用。
	 * 这可能因“内存不足”而出错，因此在其他任何分配之前执行此操作以避免泄漏。
	 */
#ifndef FRONTEND
	ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner);
#endif

	fc_ctx = ALLOC(sizeof(pg_cryptohash_ctx));
	if (fc_ctx == NULL)
		return NULL;
	memset(fc_ctx, 0, sizeof(pg_cryptohash_ctx));
	fc_ctx->type = fc_type;
	fc_ctx->error = PG_CRYPTOHASH_ERROR_NONE;
	fc_ctx->errreason = NULL;

	/*
	 * 初始化处理为 OpenSSL 分配正确类型的工作。
	 * 还确保没有来自先前运行的未处理错误在队列中。
	 */
	ERR_clear_error();
	fc_ctx->evpctx = EVP_MD_CTX_create();

	if (fc_ctx->evpctx == NULL)
	{
		explicit_bzero(fc_ctx, sizeof(pg_cryptohash_ctx));
		FREE(fc_ctx);
#ifndef FRONTEND
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory")));
#else
		return NULL;
#endif
	}

#ifndef FRONTEND
	fc_ctx->resowner = CurrentResourceOwner;
	ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
									PointerGetDatum(fc_ctx));
#endif

	return fc_ctx;
}

/*
 * pg_cryptohash_init
 *
 * 初始化一个哈希上下文。 成功时返回 0，失败时返回 -1。
 */
int pg_cryptohash_init(pg_cryptohash_ctx *fc_ctx)
{
	int			fc_status = 0;

	if (fc_ctx == NULL)
		return -1;

	switch (fc_ctx->type)
	{
		case PG_MD5:
			fc_status = EVP_DigestInit_ex(fc_ctx->evpctx, EVP_md5(), NULL);
			break;
		case PG_SHA1:
			fc_status = EVP_DigestInit_ex(fc_ctx->evpctx, EVP_sha1(), NULL);
			break;
		case PG_SHA224:
			fc_status = EVP_DigestInit_ex(fc_ctx->evpctx, EVP_sha224(), NULL);
			break;
		case PG_SHA256:
			fc_status = EVP_DigestInit_ex(fc_ctx->evpctx, EVP_sha256(), NULL);
			break;
		case PG_SHA384:
			fc_status = EVP_DigestInit_ex(fc_ctx->evpctx, EVP_sha384(), NULL);
			break;
		case PG_SHA512:
			fc_status = EVP_DigestInit_ex(fc_ctx->evpctx, EVP_sha512(), NULL);
			break;
#ifdef FDDGM
		case PG_SM3:
			fc_status = EVP_DigestInit_ex(fc_ctx->evpctx, EVP_sm3(), NULL);
			break;
#endif
	}

	/* OpenSSL 内部成功时返回 1，失败时返回 0 */
	if (fc_status <= 0)
	{
		fc_ctx->errreason = fc_SSLerrmessage(ERR_get_error());
		fc_ctx->error = PG_CRYPTOHASH_ERROR_OPENSSL;

		/*
		 * OpenSSL 错误队列通常应为空，因为我们已经
		 * 消耗了一个错误，但在启用 FIPS 的 OpenSSL 构建中，密码初始化可能会生成两个错误，因此在这里也清除队列。
		 */
		ERR_clear_error();
		return -1;
	}
	return 0;
}

/*
 * pg_cryptohash_update
 *
 * 更新哈希上下文。 成功时返回 0，失败时返回 -1。
 */
int pg_cryptohash_update(pg_cryptohash_ctx *fc_ctx, const uint8 *fc_data, size_t fc_len)
{
	int			fc_status = 0;

	if (fc_ctx == NULL)
		return -1;

	fc_status = EVP_DigestUpdate(fc_ctx->evpctx, fc_data, fc_len);

	/* OpenSSL 内部成功时返回 1，失败时返回 0 */
	if (fc_status <= 0)
	{
		fc_ctx->errreason = fc_SSLerrmessage(ERR_get_error());
		fc_ctx->error = PG_CRYPTOHASH_ERROR_OPENSSL;
		return -1;
	}
	return 0;
}

/*
 * pg_cryptohash_final
 *
 * 完成哈希上下文。 成功时返回 0，失败时返回 -1。
 */
int pg_cryptohash_final(pg_cryptohash_ctx *fc_ctx, uint8 *fc_dest, size_t fc_len)
{
	int			fc_status = 0;

	if (fc_ctx == NULL)
		return -1;

	switch (fc_ctx->type)
	{
		case PG_MD5:
			if (fc_len < MD5_DIGEST_LENGTH)
			{
				fc_ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
				return -1;
			}
			break;
		case PG_SHA1:
			if (fc_len < SHA1_DIGEST_LENGTH)
			{
				fc_ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
				return -1;
			}
			break;
		case PG_SHA224:
			if (fc_len < PG_SHA224_DIGEST_LENGTH)
			{
				fc_ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
				return -1;
			}
			break;
		case PG_SHA256:
			if (fc_len < PG_SHA256_DIGEST_LENGTH)
			{
				fc_ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
				return -1;
			}
			break;
		case PG_SHA384:
			if (fc_len < PG_SHA384_DIGEST_LENGTH)
			{
				fc_ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
				return -1;
			}
			break;
		case PG_SHA512:
			if (fc_len < PG_SHA512_DIGEST_LENGTH)
			{
				fc_ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
				return -1;
			}
			break;
#ifdef FDDGM
		case PG_SM3:
			if (fc_len < PG_SM3_DIGEST_LENGTH)
			{
				fc_ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
				return -1;
			}
			break;
#endif
	}

	fc_status = EVP_DigestFinal_ex(fc_ctx->evpctx, fc_dest, 0);

	/* OpenSSL 内部成功时返回 1，失败时返回 0 */
	if (fc_status <= 0)
	{
		fc_ctx->errreason = fc_SSLerrmessage(ERR_get_error());
		fc_ctx->error = PG_CRYPTOHASH_ERROR_OPENSSL;
		return -1;
	}
	return 0;
}

/*
 * pg_cryptohash_free
 *
 * 释放哈希上下文。
 */
void pg_cryptohash_free(pg_cryptohash_ctx *fc_ctx)
{
	if (fc_ctx == NULL)
		return;

	EVP_MD_CTX_destroy(fc_ctx->evpctx);

#ifndef FRONTEND
	ResourceOwnerForgetCryptoHash(fc_ctx->resowner,
								  PointerGetDatum(fc_ctx));
#endif

	explicit_bzero(fc_ctx, sizeof(pg_cryptohash_ctx));
	FREE(fc_ctx);
}

/*
 * pg_cryptohash_error
 *
 * 返回一个静态字符串，提供有关计算过程中发生的错误的详细信息。
 */
const char * pg_cryptohash_error(pg_cryptohash_ctx *fc_ctx)
{
	/*
	 * 此实现永远不会因内存不足错误而失败，
	 * 除非在创建上下文时。
	 */
	if (fc_ctx == NULL)
		return _("out of memory");

	/*
	 * 如果提供了原因，则依赖于它，否则退回到设置的任何错误代码。
	 */
	if (fc_ctx->errreason)
		return fc_ctx->errreason;

	switch (fc_ctx->error)
	{
		case PG_CRYPTOHASH_ERROR_NONE:
			return _("success");
		case PG_CRYPTOHASH_ERROR_DEST_LEN:
			return _("destination buffer too small");
		case PG_CRYPTOHASH_ERROR_OPENSSL:
			return _("OpenSSL failure");
	}

	Assert(false);				/* 无法到达 */
	return _("success");
}
